package util;

import static util.Constants.BOARD_HEIGHT;
import static util.Constants.BOARD_WIDTH;
import static util.Strings.STR_NOT_VALID_ABS;
import static util.Strings.STR_NOT_VALID_ORD;

import java.util.HashSet;
import java.util.Set;

/**
 * Cette classe représente un couple d'entier représentant un emplacement sur le
 * plateau de jeu. La ligne et la colonne sont contraints par la taille du
 * plateau.
 * 
 * @author Lucile Gramusset et Guillaume Poussel
 * 
 */
public class Coordinate implements Comparable<Coordinate> {

    /**
     * Ligne
     */
    private int ligne;

    /**
     * Colonne
     */
    private int colonne;

    /**
     * Construit une coordonée à partir de la ligne et de la colonne
     * 
     * @param ligne
     *            Ligne
     * @param colonne
     *            Colonne
     * @exception IllegalArgumentException
     *                La ligne ou la colonne ne sont pas dans les limites du
     *                plateau.
     */
    public Coordinate(int ligne, int colonne) {
        if (ligne < 0 || ligne >= BOARD_WIDTH)
            throw new IllegalArgumentException(String.format(STR_NOT_VALID_ABS,
                    ligne));
        if (colonne < 0 || colonne >= BOARD_HEIGHT)
            throw new IllegalArgumentException(String.format(STR_NOT_VALID_ORD,
                    colonne));
        this.ligne = ligne;
        this.colonne = colonne;
    }

    /**
     * Permet de mettre les coordonées en clef d'une HashMap.
     * 
     * @return L'identifiant représentant une coordonée.
     */
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ligne;
        result = prime * result + colonne;
        return result;
    }

    /**
     * Cette méthode permet de comparer deux coordonées en comparant leur ligne
     * et leur colonne
     * 
     * @return <code>true</code> si les deux coordonées ont la même ligne et la
     *         même colonne, <code>false</code> sinon.
     */
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Coordinate other = (Coordinate) obj;
        if (ligne != other.ligne)
            return false;
        if (colonne != other.colonne)
            return false;
        return true;
    }

    /**
     * Représente la coordonée sous la forme usuelle. Par exemple, pour la
     * première ligne et la deuxième colonne, on obtient {@code B1}.
     * 
     * @return Une lettre représentant la colonne suivie d'un chiffre
     *         représentant la ligne.
     */
    public String toString() {
        return String.valueOf("ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(colonne))
                + (ligne + 1);
    }

    /**
     * Retourne la ligne
     * 
     * @return La ligne
     */
    public int getLigne() {
        return ligne;
    }

    /**
     * Retourne la colonne
     * 
     * @return La colonne
     */
    public int getColonne() {
        return colonne;
    }

    /**
     * Retourne la coordonée de la case située juste au-dessus.
     * 
     * @return <code>null</code> si le bord du plateau a été atteint, la
     *         coordonée de la case au-dessus sinon.
     */
    public Coordinate north() {
        try {
            return new Coordinate(this.ligne - 1, this.colonne);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Retourne la coordonée de la case située juste en-dessous.
     * 
     * @return <code>null</code> si le bord du plateau a été atteint, la
     *         coordonée de la case en-dessous sinon.
     */
    public Coordinate south() {
        try {
            return new Coordinate(this.ligne + 1, this.colonne);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Retourne la coordonée de la case située juste à droite.
     * 
     * @return <code>null</code> si le bord du plateau a été atteint, la
     *         coordonée de la case à droite sinon.
     */
    public Coordinate east() {
        try {
            return new Coordinate(this.ligne, this.colonne + 1);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Retourne la coordonée de la case située juste à gauche.
     * 
     * @return <code>null</code> si le bord du plateau a été atteint, la
     *         coordonée de la case à gauche sinon.
     */
    public Coordinate west() {
        try {
            return new Coordinate(this.ligne, this.colonne - 1);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Retourne l'ensemble des coordonées situées autour de cette case (au nord,
     * au sud, à l'est et à l'ouest).
     * 
     * @return Un ensemble de taille comprise entre 2 et 4.
     */
    public Set<Coordinate> aroundCoordinates() {
        Set<Coordinate> around = new HashSet<Coordinate>();
        if (north() != null)
            around.add(north());
        if (south() != null)
            around.add(south());
        if (west() != null)
            around.add(west());
        if (east() != null)
            around.add(east());
        return around;
    }

    /**
     * Cette méthode est spécifiée par l'interface {@link Comparable }. Elle
     * permet de trier les coordonées en fonction de leur position sur le
     * plateau. La plus petite case est en haut à gauche, la plus grand en bas à
     * droite.
     */
    public int compareTo(Coordinate other) {
        if (this.getLigne() == other.getLigne()) {
            return this.getColonne() - other.getColonne();
        } else {
            return this.getLigne() - other.getLigne();
        }
    }

}
